package logic.pathelements;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Random;
import logic.connectors.NetworkInterface;

/**
 * Represents Firewall element. Has two interfaces:
 * 0 - local interface, 1 - external interface.
 * @author Dmitry Kuznetsov <zendipy@gmail.com>
 * @version 1.2
 */
public class Firewall extends ActiveElement {


	private static final long serialVersionUID = -7390043613442549722L;
	// Random number generator
	private static Random rnd = new Random();
	// List of the rules
	private ArrayList<FirewallRule> rules;
	
	/**
	 * Constructor. Execute parent constructor of {@link ActiveElement}
	 * @param id						element id
	 * @param name						element name
	 * @param networkInterfacesCount	quantity of interfaces
	 * @throws Exception				exception
	 */
     public Firewall(Integer id, String name) throws Exception {
        super(id, name, 2);
        rules = new ArrayList<FirewallRule>();
     }
		
	/**
	 * Add new rule to firewall.
	 * @param name		name of the rule
	 * @param protocol  protocol type
	 * @param srcPort	source port number
	 * @param destPort	destination port number
	 * @param srcIp		source ip-address
	 * @param destIp	destination ip-address
	 * @param action	action (1 - accept, 0 - deny)
	 * 
	 * @throws IllegalArgumentException if rule with the 'name' already exists 
	 */
	public void addNewRule(String name, Protocol protocol, int srcPort, 
						   int destPort,InetAddress srcIp,InetAddress destIp, byte action){
		int index = searchByName(name);
		if (index == -1){
			FirewallRule fr = new FirewallRule(	name, 
												protocol, 
												srcPort, destPort, 
												srcIp, destIp, 
												action);
			
			rules.add(fr);
		}
		else
			throw new IllegalArgumentException("The rule with the name \'"+name+
												"\' is already exists!");
	}
	
	
	/**
	 * Remove one rule by index.
	 * @param index	number of the rule
	 * 
	 * @throws IllegalArgumentException if there is no such index
	 */
	public void removeRule(int index){
		if (index > rules.size()-1 || index < 0)
			throw new IllegalArgumentException("There is no rule with index "+index+ "!");
		else
			rules.remove(index);
	}
	
	/**
	 * Remove one rule by name.
	 * @param name	name of the rule
	 * @throws IllegalArgumentException if there is no rule with the 'name'
	 */
	public void removeRule(String name){
		int index = searchByName(name);
		// if such rule exists, remove it
		if (index != -1)
			rules.remove(index);
		else
			// if not, throw exception
			throw new IllegalArgumentException("There is no rule with the name \'"+name+"\'!");
	}
	
	/**
	 * Get full list of the rules in text format.
	 * @return String array of rules text
	 */
	public String[] getRuleList(){
		int size = rules.size();
		String[] result = new String[size];
		for (int i = 0; i < size; i++){
			result[i] = i + ": "+rules.get(i).toString();
		}
		return result;
	}
	
	/**
	 * Get text of the rule by index
	 * @param index	number of the rule
	 * @return text of the rule and null if the rule is not found
	 */
	public String getRule(int index){
		if (index > rules.size()-1 || index < 0)
			return null;
		return rules.get(index).toString();
	}
	
	/**
	 * Get text of the rule by name.
	 * @param name name of the rule
	 * @return text of the rule and @null if the rule is not found
	 */
	public String getRule(String name){
		int index = searchByName(name);
		if (index != -1)
			return rules.get(index).toString();
		// if not found return null
		return null;
	}
	
	/**
	 * Check network packet
	 * @param protocol	protocol type
	 * @param srcPort	source port number
	 * @param destPort	destination port number
	 * @param srcIp		source ip-address 
	 * @param destIp	destination ip-address
	 * @return			external {@link NetworkInterface} if accepted and null if not
	 */
	public NetworkInterface examine(Protocol protocol, int srcPort,int destPort,
						InetAddress srcIp,InetAddress destIp){
		// if no rules in firewall
		if(rules.size() == 0){
			return (NetworkInterface) connectors.get(1);
		}
		// checking rules
		if(!checkPacket(protocol, srcPort, destPort, srcIp, destIp)){
			return null;
		}
		// if no one rule permit the packet. Accept it.
		return (NetworkInterface) connectors.get(1);
		
	}
	
	/**
	 * Check firewall rules
	 * @param protocol	protocol type
	 * @param srcPort	source port number
	 * @param destPort	destination port number
	 * @param srcIp		source ip-address 
	 * @param destIp	destination ip-address
	 * @return			result of examination (by default true)
	 */
	private boolean checkPacket(Protocol protocol, int srcPort, int destPort,
			InetAddress srcIp,InetAddress destIp){
		boolean accept = true;
				
		// search by protocol
		for (FirewallRule r : rules){
			if (r.getProtocolType().equals(protocol)){
				// check other paraments of the packet
				if (r.getSrcAddr().equals(srcIp) 	|| r.getSrcPort() == srcPort ||
					r.getDestAddr().equals(destIp) 	|| r.getDestPort() == destPort){
					switch(r.getAction()){
						case 0:				// DENY
							accept = false;
							break;
						case 1:				// ACCEPT
							accept = true;
							break;
					}
				}
			}
		}
		
		return accept;
	
	}
	/**
	 * Search rule by name in the rule list.
	 * @param name rule name
	 * @return position in the list and -1 if the rule is not found
	 */
	private int searchByName(String name){
		for (int i = 0; i < rules.size(); i++){
			// if found, remove from list
			if (rules.get(i).getName().equals(name)){
				return i;				
			}
		}
		return -1;
	}

	/**
	 * Get cost of the transmission
	 * @return random cost from 0 to 1000
	 */
	@Override
	public double getCosts() {
		return rnd.nextInt(1000);
	}

	
	/**
	 * Get information of this device.
	 * @return string with the info
	 */
	@Override
	public String getInfo() {
		String info = 
				"Firewall. Id: " + this.id +
				", Name: " + this.name +
				", ClassName:" + this.getClass().getName();
		return info; 
	}

	/**
	 * Generate delay from 0s to 10000ms at random
	 * @return milliseconds of delay
	 */
	@Override
	public double getTimeDelay() {
		return rnd.nextInt(10001);
	}
	
	
}
